/*
 * Copyright (c) 2023. The RigelA open source project team and
 * its contributors reserve all rights.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

use std::ffi::{c_void, CString};
pub use windows::{
    core::{Result, HRESULT},
    Win32::{
        Foundation::{
            FARPROC, HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, LRESULT, RECT, WAIT_EVENT, WPARAM,
        },
        System::SystemServices::{
            DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH,
        },
        UI::WindowsAndMessaging::{
            CW_USEDEFAULT, DWL_DLGPROC, DWL_MSGRESULT, DWL_USER, GWL_EXSTYLE, GWL_HINSTANCE,
            GWL_HWNDPARENT, GWL_ID, GWL_STYLE, GWL_USERDATA, GWL_WNDPROC, HHOOK, HMENU, HOOKPROC,
            MB_ABORTRETRYIGNORE, MB_APPLMODAL, MB_CANCELTRYCONTINUE, MB_DEFAULT_DESKTOP_ONLY,
            MB_DEFBUTTON1, MB_DEFBUTTON2, MB_DEFBUTTON3, MB_DEFBUTTON4, MB_DEFMASK, MB_HELP,
            MB_ICONASTERISK, MB_ICONERROR, MB_ICONEXCLAMATION, MB_ICONHAND, MB_ICONINFORMATION,
            MB_ICONMASK, MB_ICONQUESTION, MB_ICONSTOP, MB_ICONWARNING, MB_MISCMASK, MB_MODEMASK,
            MB_NOFOCUS, MB_OK, MB_OKCANCEL, MB_RETRYCANCEL, MB_RIGHT, MB_RTLREADING,
            MB_SERVICE_NOTIFICATION, MB_SERVICE_NOTIFICATION_NT3X, MB_SETFOREGROUND,
            MB_SYSTEMMODAL, MB_TASKMODAL, MB_TOPMOST, MB_TYPEMASK, MB_USERICON, MB_YESNO,
            MB_YESNOCANCEL, SHOW_WINDOW_CMD, SW_FORCEMINIMIZE, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE,
            SW_NORMAL, SW_RESTORE, SW_SHOW, SW_SHOWDEFAULT, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED,
            SW_SHOWMINNOACTIVE, SW_SHOWNA, SW_SHOWNOACTIVATE, SW_SHOWNORMAL, WINDOWS_HOOK_ID,
            WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WINDOW_STYLE, WNDPROC, WS_ACTIVECAPTION,
            WS_BORDER, WS_CAPTION, WS_CHILD, WS_CHILDWINDOW, WS_CLIPCHILDREN, WS_CLIPSIBLINGS,
            WS_DISABLED, WS_DLGFRAME, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_CLIENTEDGE,
            WS_EX_COMPOSITED, WS_EX_CONTEXTHELP, WS_EX_CONTROLPARENT, WS_EX_DLGMODALFRAME,
            WS_EX_LAYERED, WS_EX_LAYOUTRTL, WS_EX_LEFT, WS_EX_LEFTSCROLLBAR, WS_EX_LTRREADING,
            WS_EX_MDICHILD, WS_EX_NOACTIVATE, WS_EX_NOINHERITLAYOUT, WS_EX_NOPARENTNOTIFY,
            WS_EX_NOREDIRECTIONBITMAP, WS_EX_OVERLAPPEDWINDOW, WS_EX_PALETTEWINDOW, WS_EX_RIGHT,
            WS_EX_RIGHTSCROLLBAR, WS_EX_RTLREADING, WS_EX_STATICEDGE, WS_EX_TOOLWINDOW,
            WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_GROUP, WS_HSCROLL, WS_ICONIC,
            WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPED,
            WS_OVERLAPPEDWINDOW, WS_POPUP, WS_POPUPWINDOW, WS_SIZEBOX, WS_SYSMENU, WS_TABSTOP,
            WS_THICKFRAME, WS_TILED, WS_TILEDWINDOW, WS_VISIBLE, WS_VSCROLL,
        },
    },
};
use windows::{
    core::{HSTRING, PCSTR},
    Win32::{
        Foundation::{CloseHandle, FreeLibrary, GetLastError, MAX_PATH, WIN32_ERROR},
        Globalization::{GetUserDefaultLocaleName, MAX_LOCALE_NAME},
        System::{
            Diagnostics::Debug::Beep,
            LibraryLoader::{GetModuleFileNameW, GetModuleHandleW, GetProcAddress, LoadLibraryW},
            Threading::AttachThreadInput,
        },
        UI::WindowsAndMessaging::{
            CallNextHookEx, CallWindowProcW, CreateWindowExW, DestroyWindow, FindWindowExW,
            FindWindowW, GetClassNameW, GetDesktopWindow, GetForegroundWindow, GetWindowLongPtrW,
            GetWindowLongW, GetWindowTextW, MessageBoxW, SetForegroundWindow, SetWindowLongPtrW,
            SetWindowLongW, SetWindowsHookExW, ShowWindow, UnhookWindowsHookEx, MESSAGEBOX_STYLE,
        },
    },
};

/**
播放一个声音。
`freq` 声音频率（Hz）
`duration` 持续时间（毫秒）
*/
pub fn beep(freq: u32, duration: u32) {
    unsafe { Beep(freq, duration) }.unwrap();
}

/**
查询用户当前窗口（前台窗口的句柄）。
系统为创建前台窗口的线程分配的优先级略高于其他线程。
返回值是前台窗口的句柄。
在某些情况下，前台窗口可以为NULL，例如，当窗口失去激活时。
*/
pub fn get_foreground_window() -> HWND {
    unsafe { GetForegroundWindow() }
}

/**
将创建指定窗口的线程引入前台并激活窗口。
键盘输入将定向到窗口，并为用户更改各种视觉提示。
系统为创建前台窗口的线程分配的优先级略高于其他线程的优先级。
`h_wnd` 应激活并带到前台的窗口的句柄。
*/
pub fn set_foreground_window(h_wnd: HWND) -> bool {
    unsafe { SetForegroundWindow(h_wnd) }.as_bool()
}

/**
将一个线程的输入处理机制附加到或分离另一个线程的输入处理机制。
通过使用 attach_thread_input 函数，线程可以共享其输入状态 (例如键盘状态，当前焦点窗口) 另一个线程。 通过再次调用 attach_thread_input 并为 attach 参数指定 FALSE，将按照两个线程接收的顺序处理这两个线程接收的键盘和鼠标事件，直到这些线程被分离。
如果任一指定的线程没有消息队列， 则此函数将失败。当线程首次调用 USER 或 GDI 函数之一时，系统会创建线程的消息队列。
如果安装了日志记录挂钩， attach_thread_input 函数也会失败。日志记录挂钩将所有输入队列附加到一起。
请注意，键状态（可通过调用 get_key_state 或 get_keyboard_state 函数确定）在调用 attach_thread_input 后重置。
不能将线程附加到另一个桌面中的线程。
`id_attach` 要附加到另一个线程的线程的标识符。要附加的线程不能是系统线程。
`id_attach_to` 将附加到的线程的标识符。此线程不能是系统线程。线程无法附加到自身。因此， id_attach_to 不能等于 id_attach。
`attach` 如果此参数为 TRUE，则附加两个线程。如果参数为 FALSE，则分离线程。
*/
pub fn attach_thread_input(id_attach: u32, id_attach_to: u32, attach: bool) -> bool {
    unsafe { AttachThreadInput(id_attach, id_attach_to, attach) }.as_bool()
}

//noinspection SpellCheckingInspection
/**
创建具有扩展窗口样式的重叠窗口、弹出窗口或子窗口。
create_window_ex 函数将WM_NCCREATE、WM_NCCALCSIZE和WM_CREATE消息发送到正在创建的窗口。
如果创建的窗口是子窗口，则其默认位置位于 Z 顺序的底部。 如果创建的窗口是顶级窗口，则其默认位置位于 Z 顺序 (的顶部，但位于所有最顶层窗口下方，除非创建的窗口本身是最顶层) 。
有关控制任务栏是否显示已创建窗口按钮的信息，请参阅 管理任务栏按钮。
有关删除窗口的信息，请参阅 destroy_window 函数。
可以在 class_name 参数中指定以下预定义控件类。 请注意可在 style 参数中使用的相应控件样式。
类 | 含义
BUTTON | 指定一个小矩形子窗口，该窗口表示用户可以单击以将其打开或关闭的按钮。 按钮控件可以单独使用，也可以成组使用，并且可以不带文本标记或显示。 当用户单击按钮控件时，按钮控件通常会更改外观。 有关详细信息，请参阅 按钮。有关可在 style 参数中指定的按钮样式表，请参阅 按钮样式。
COMBOBOX | 指定由列表框和类似于编辑控件的选择字段组成的控件。 使用此样式时，应用程序应随时显示列表框或启用下拉列表框。 如果列表框可见，在选择字段中键入字符会突出显示与键入的字符匹配的第一个列表框条目。 相反，选择列表框中的项会在选择字段中显示所选文本。 有关详细信息，请参阅 组合框。有关可以在 style 参数中指定的组合框样式表，请参阅 组合框样式。
EDIT | 指定一个矩形子窗口，用户可以从键盘键入文本。 用户选择控件，并通过单击控件或通过按 TAB 键移动到控件来为它提供键盘焦点。 编辑控件显示闪烁的插入点时，用户可以键入文本;使用鼠标移动光标，选择要替换的字符，或定位光标以插入字符：或使用 键删除字符。 有关详细信息，请参阅 编辑控件。有关可在 style 参数中指定的编辑控件样式的表，请参阅 编辑控件样式。
LISTBOX | 指定字符串列表。 每当应用程序必须提供用户可从中选择的名称列表（如文件名）时，请指定此控件。 用户可以通过单击来选择字符串。 突出显示所选字符串，并将通知消息传递到父窗口。 有关详细信息，请参阅 列表框。有关可在 style 参数中指定的列表框样式表，请参阅 列表框样式。
MDICLIENT | 指定 MDI 客户端窗口。 此窗口接收控制 MDI 应用程序的子窗口的消息。 建议的样式位是 WS_CLIPCHILDREN 和 WS_CHILD。 指定 WS_HSCROLL 和 WS_VSCROLL 样式以创建允许用户将 MDI 子窗口滚动到视图中的 MDI 客户端窗口。 有关详细信息，请参阅 多文档接口。
RichEdit | 指定 Microsoft Rich Edit 1.0 控件。 此窗口允许用户使用字符和段落格式查看和编辑文本，并且可以将嵌入式组件对象模型 (COM) 对象。 有关详细信息，请参阅 Rich Edit 控件。有关可以在 style 参数中指定的丰富编辑控件样式的表，请参阅 Rich Edit 控件样式。
RICHEDIT_CLASS | 指定 Microsoft Rich Edit 2.0 控件。 此控件允许用户使用字符和段落格式查看和编辑文本，并且可以包含嵌入的 COM 对象。 有关详细信息，请参阅 Rich Edit 控件。有关可以在 style 参数中指定的丰富编辑控件样式的表，请参阅 Rich Edit 控件样式。
SCROLLBAR | 指定一个矩形，该矩形包含一个滚动框，两端都有方向箭头。 每当用户单击控件时，滚动条就会向其父窗口发送通知消息。 如有必要，父窗口负责更新滚动框的位置。 有关详细信息，请参阅 滚动条。有关可在 style 参数中指定的滚动条控件样式的表，请参阅 滚动条控件样式。
STATIC | 指定用于标记、框或分隔其他控件的简单文本字段、框或矩形。 静态控件不采用任何输入，也不提供任何输出。 有关详细信息，请参阅 静态控件。有关可在 style 参数中指定的静态控件样式表，请参阅 静态控件样式。
ex_style的WS_EX_NOACTIVATE值阻止系统进行前台激活。 若要防止在用户单击窗口时激活队列，必须正确处理 WM_MOUSEACTIVATE 消息。 若要将窗口带到前台或以编程方式激活它，请使用 set_foreground_window 或 set_active_window。 将 FALSE 返回到 WM_NCACTIVATE 可防止窗口丢失队列激活。 但是，在激活时将忽略返回值。
设置 WS_EX_COMPOSITED 后，窗口的所有后代使用双缓冲获取从下到上绘制顺序。 从下到上绘制顺序允许后代窗口具有半透明 (alpha) 和透明度 (颜色键) 效果，但前提是后代窗口还设置了 WS_EX_TRANSPARENT 位。 通过双重缓冲，可以在不闪烁的情况下绘制窗口及其后代。
`ex_style` 正在创建的窗口的扩展窗口样式。 有关可能值的列表，请参阅 扩展窗口样式。
`class_name` 指定窗口类名称。 类名可以是使用 register_class 或 register_class_ex 注册的任何名称，前提是注册该类的模块也是创建窗口的模块。 类名也可以是任何预定义 的系统类 名称。
`window_name` 窗口名称。 如果窗口样式指定标题栏，则 window_name 指向的窗口标题将显示在标题栏中。 使用 create_window 创建控件（如按钮、检查框和静态控件）时，请使用 window_name 指定控件的文本。 使用 SS_ICON 样式创建静态控件时，请使用 window_name 指定图标名称或标识符。 若要指定标识符，请使用语法“#num”。
`style` 正在创建的窗口的样式。 此参数可以是 窗口样式值以及“备注”部分中指示的控件样式的组合。
`x` 窗口的初始水平位置。 对于重叠或弹出窗口， x 参数是窗口左上角的初始 x 坐标（以屏幕坐标表示）。 对于子窗口， x 是窗口左上角相对于父窗口工作区左上角的 x 坐标。 如果 x 设置为 CW_USEDEFAULT，则系统会选择窗口左上角的默认位置，并忽略 y 参数。 CW_USEDEFAULT 仅对重叠窗口有效;如果为弹出窗口或子窗口指定，则 x 和 y 参数设置为零。
`y` 窗口的初始垂直位置。 对于重叠或弹出窗口， y 参数是窗口左上角的初始 y 坐标（以屏幕坐标表示）。 对于子窗口， y 是子窗口左上角相对于父窗口工作区左上角的初始 y 坐标。 对于列表框 ，y 是列表框工作区左上角相对于父窗口工作区左上角的初始 y 坐标。如果使用 WS_VISIBLE 样式位创建重叠窗口，并且 x 参数设置为 CW_USEDEFAULT，则 y 参数确定窗口的显示方式。 如果 y 参数CW_USEDEFAULT，则窗口管理器在创建窗口后使用SW_SHOW标志调用 show_window。 如果 y 参数是其他某个值，则窗口管理器调用 show_window ，该值作为 cmd_show 参数。
`width` 窗口的宽度（以设备单位为单位）。 对于重叠窗口， width 是窗口的宽度、屏幕坐标或 CW_USEDEFAULT。 如果 width=CW_USEDEFAULT，系统将为窗口选择默认宽度和高度;默认宽度从初始 x 坐标扩展到屏幕的右边缘;默认高度从初始 y 坐标扩展到图标区域的顶部。 CW_USEDEFAULT 仅对重叠窗口有效;如果为弹出窗口或子窗口指定 了CW_USEDEFAULT ，则 width 和 height 参数设置为零。
`height` 窗口的高度（以设备单位为单位）。 对于重叠窗口， height 是窗口的高度（以屏幕坐标为单位）。 如果 width 参数设置为 CW_USEDEFAULT，则系统将忽略 height。
`h_wnd_parent` 正在创建的窗口的父窗口或所有者窗口的句柄。 若要创建子窗口或拥有的窗口，请提供有效的窗口句柄。 此参数对于弹出窗口是可选的。若要创建 仅消息窗口，请向现有仅消息窗口提供 HWND_MESSAGE 或句柄。
`h_menu` 菜单句柄，或指定子窗口标识符，具体取决于窗口样式。 对于重叠或弹出窗口， h_menu 标识要与窗口一起使用的菜单;如果要使用类菜单，则它可以为 NULL 。 对于子窗口， h_menu 指定子窗口标识符，即对话框控件用于通知其父级事件的整数值。 应用程序确定子窗口标识符;对于具有相同父窗口的所有子窗口，它必须是唯一的。
`h_instance` 要与窗口关联的模块实例的句柄。
`param` 指向要通过 CREATESTRUCT 结构传递到窗口的值的指针， (WM_CREATE消息的 lParam 参数指向的 lpCreateParams 成员) 。 此消息在返回之前由此函数发送到创建的窗口。如果应用程序调用 create_window 来创建 MDI 客户端窗口， lpParam 应指向 CLIENTCREATESTRUCT 结构。 如果 MDI 客户端窗口调用 create_window 来创建 MDI 子窗口， lpParam 应指向 MDICREATESTRUCT 结构。 如果不需要其他数据，lpParam 可能为 NULL。
*/
pub fn create_window_ex(
    ex_style: WINDOW_EX_STYLE,
    class_name: &str,
    window_name: &str,
    style: WINDOW_STYLE,
    x: i32,
    y: i32,
    width: i32,
    height: i32,
    h_wnd_parent: Option<HWND>,
    h_menu: Option<HMENU>,
    h_instance: Option<HINSTANCE>,
    param: Option<*const c_void>,
) -> HWND {
    unsafe {
        CreateWindowExW(
            ex_style,
            &HSTRING::from(class_name),
            &HSTRING::from(window_name),
            style,
            x,
            y,
            width,
            height,
            h_wnd_parent,
            h_menu,
            h_instance,
            param,
        )
        .unwrap_or(Default::default())
    }
}

//noinspection SpellCheckingInspection
/**
销毁指定的窗口。 函数将 WM_DESTROY 和 WM_NCDESTROY 消息发送到窗口，以将其停用，并从窗口中删除键盘焦点。 函数还会销毁窗口菜单、刷新线程消息队列、销毁计时器、删除剪贴板所有权，并中断剪贴板查看器链 (如果窗口位于查看器链的顶部)。
如果指定的窗口是父窗口或所有者窗口， 则 destroy_window 会在销毁父窗口或所有者窗口时自动销毁关联的子窗口或拥有窗口。 函数首先销毁子窗口或拥有的窗口，然后销毁父窗口或所有者窗口。
destroy_window 还会销毁 create_dialog 函数创建的无模式对话框。
线程不能使用 destroy_window 来销毁由其他线程创建的窗口。
如果要销毁的窗口是没有 WS_EX_NOPARENTNOTIFY 样式的子窗口，则会向父窗口发送 WM_PARENTNOTIFY 消息。
`h_wnd` 要销毁的窗口的句柄。
*/
pub fn destroy_window(h_wnd: HWND) -> bool {
    unsafe { DestroyWindow(h_wnd) }.is_ok()
}

/**
查询桌面窗口的句柄。
桌面窗口覆盖整个屏幕。
桌面窗口是在上面绘制其他窗口的区域。
*/
pub fn get_desktop_window() -> HWND {
    unsafe { GetDesktopWindow() }
}

/**
查询处理顶级窗口的类名和窗口名称匹配指定的字符串。这个函数不搜索子窗口。
`class_name` 用来指定类名的字符串或一个可以确定类名字符串的原子。如果这个参数是一个原子，那么它必须是一个在调用此函数前已经通过GlobalAddAtom函数创建好的全局原子。这个原子（一个16bit的值），必须被放置在class_name的低位字节中，class_name的高位字节置零。如果该参数为null时，将会寻找任何与window_name参数匹配的窗口。
`window_name` 用来指定窗口名（即窗口标题）的字符串。如果此参数为NULL，则匹配所有窗口名。
*/
pub fn find_window(class_name: Option<&str>, window_name: Option<&str>) -> HWND {
    unsafe {
        match (class_name, window_name) {
            (Some(c), Some(w)) => FindWindowW(&HSTRING::from(c), &HSTRING::from(w)),
            (Some(c), None) => FindWindowW(&HSTRING::from(c), None),
            (None, Some(w)) => FindWindowW(None, &HSTRING::from(w)),
            _ => FindWindowW(None, None),
        }
        .unwrap_or(Default::default())
    }
}

//noinspection SpellCheckingInspection
/**
查询其类名和窗口名称与指定字符串匹配的窗口的句柄。 函数搜索子窗口，从指定子窗口后面的子窗口开始。 此函数不执行区分大小写的搜索。
find_window_ex 函数仅搜索直接子窗口。 它不搜索其他后代。
如果 window_name 参数不为 NULL，find_window_ex 将调用 get_window_text 函数以查询窗口名称进行比较。 有关可能出现的潜在问题的说明，请参阅 get_window_text。
`h_wnd_parent` 要搜索其子窗口的父窗口的句柄。如果 h_wnd_parent 为 NULL，则该函数使用桌面窗口作为父窗口。 函数在桌面子窗口的窗口之间搜索。如果 h_wnd_parent=HWND_MESSAGE，该函数将搜索所有 仅消息窗口。
`h_wnd_child_after` 子窗口的句柄。搜索从Z顺序中的下一个子窗口开始。子窗口必须是h_wnd_parent的直接子窗口，而不仅仅是后代窗口。如果h_wnd_child_after为NULL，则搜索从h_wnd_parent的第一个子窗口开始。
请注意，如果 h_wnd_parent 和 h_wnd_child_after 均为 NULL，则该函数将搜索所有顶级窗口和仅消息窗口。
`class_name` 上一次调用 register_class 或 register_class_ex 函数创建的类名或类原子。 原子必须放置在 class_name 的低序字中;高序字必须为零。如果 class_name 是字符串，则指定窗口类名称。 类名可以是使用 register_class 或 register_class_ex 注册的任何名称，也可以是任何预定义的控件类名称，也可以是 MAKEINTATOM(0x8000)。 在后一种情况下，0x8000是菜单类的原子。
`window_name` (窗口名称窗口标题)。 如果此参数为 NULL，则所有窗口名称都匹配。
*/
pub fn find_window_ex(
    h_wnd_parent: Option<HWND>,
    h_wnd_child_after: Option<HWND>,
    class_name: Option<&str>,
    window_name: Option<&str>,
) -> Option<HWND> {
    unsafe {
        match (class_name, window_name) {
            (Some(c), Some(w)) => FindWindowExW(
                h_wnd_parent,
                h_wnd_child_after,
                &HSTRING::from(c),
                &HSTRING::from(w),
            ),
            (Some(c), None) => {
                FindWindowExW(h_wnd_parent, h_wnd_child_after, &HSTRING::from(c), None)
            }
            (None, Some(w)) => {
                FindWindowExW(h_wnd_parent, h_wnd_child_after, None, &HSTRING::from(w))
            }
            _ => FindWindowExW(h_wnd_parent, h_wnd_child_after, None, None),
        }
        .ok()
    }
}

/**
如果目标窗口由当前进程拥有，则get_window_text将会把WM_GETTEXT消息发送到指定的窗口或控件。
如果目标窗口由另一个进程拥有，并且具有描述文字，则get_window_text将查询描述文字文本的窗口。
如果窗口没有描述文字，则返回值为 null 字符串。此行为是设计使然。
如果拥有目标窗口的进程没有响应，它允许应用程序调用 get_window_text ，而不会变得无响应。
但是，如果目标窗口没有响应，并且它属于调用应用程序， 则 get_window_text 将导致调用应用程序变得无响应。
若要在另一个进程中查询控件的文本，请直接发送 WM_GETTEXT 消息，而不是调用 get_window_text。
`h_wnd` 包含文本的窗口或控件的句柄。
*/
pub fn get_window_text(h_wnd: HWND) -> String {
    let mut text: [u16; 255] = [0; 255];
    let len = unsafe { GetWindowTextW(h_wnd, &mut text) };
    String::from_utf16_lossy(&text[..len as usize])
}

/**
查询指定窗口所属的类的名称。
`h_wnd` 窗口的句柄，以及窗口所属的类的间接句柄。
*/
pub fn get_class_name(h_wnd: HWND) -> String {
    let mut text: [u16; 255] = [0; 255];
    let len = unsafe { GetClassNameW(h_wnd, &mut text) };
    String::from_utf16_lossy(&text[..len as usize])
}

//noinspection SpellCheckingInspection
/**
设置指定窗口的显示状态。
`h_wnd` 窗口的句柄。
`cmd_show` 控制窗口的显示方式。 如果启动应用程序的程序提供 STARTUPINFO 结构，则应用程序首次调用 ShowWindow 时将忽略此参数。 否则，首次调用 ShowWindow 时，该值应为 WinMain 函数在其 nCmdShow 参数中获取的值。 在后续调用中，此参数可以是以下值之一。
- SW_HIDE，隐藏窗口并激活另一个窗口。
- SW_SHOWNORMAL SW_NORMAL，激活并显示窗口。 如果窗口最小化、最大化或排列，系统会将其还原到其原始大小和位置。 应用程序应在首次显示窗口时指定此标志。
- SW_SHOWMINIMIZED，激活窗口并将其显示为最小化窗口。
- SW_SHOWMAXIMIZED SW_MAXIMIZE，激活窗口并显示最大化的窗口。
- SW_SHOWNOACTIVATE，以最近的大小和位置显示窗口。 此值类似于 SW_SHOWNORMAL，只是窗口未激活。
- SW_SHOW，激活窗口并以当前大小和位置显示窗口。
- SW_MINIMIZE，最小化指定的窗口，并按 Z 顺序激活下一个顶级窗口。
- SW_SHOWMINNOACTIVE，将窗口显示为最小化窗口。 此值类似于 SW_SHOWMINIMIZED，但窗口未激活。
- SW_SHOWNA，以当前大小和位置显示窗口。 此值类似于 SW_SHOW，只是窗口未激活。
- SW_RESTORE，激活并显示窗口。 如果窗口最小化、最大化或排列，系统会将其还原到其原始大小和位置。 还原最小化窗口时，应用程序应指定此标志。
- SW_SHOWDEFAULT，根据启动应用程序的程序传递给 CreateProcess 函数的 STARTUPINFO 结构中指定的SW_值设置显示状态。
- SW_FORCEMINIMIZE，最小化窗口，即使拥有窗口的线程没有响应。 仅当最小化不同线程的窗口时，才应使用此标志。
*/
pub fn show_window(h_wnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> bool {
    unsafe { ShowWindow(h_wnd, cmd_show) }.as_bool()
}

/**
调用下一个钩子函数。
`code` 传递给当前挂钩过程的挂钩代码。 下一个挂钩过程使用此代码来确定如何处理挂钩信息。
`w_param` 传递给当前挂钩过程的 wParam 值。 此参数的含义取决于与当前挂钩链关联的挂钩类型。
`l_param` 传递给当前挂钩过程的 lParam 值。 此参数的含义取决于与当前挂钩链关联的挂钩类型。
*/
pub fn call_next_hook_ex(code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
    unsafe {
        CallNextHookEx(
            None, // 此参数应该忽略： https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-callnexthookex?redirectedfrom=MSDN
            code, w_param, l_param,
        )
    }
}

/**
将应用程序定义的拦截程序安装到钩子链中。您将安装拦截程序，以监视系统是否有特定类型的事件。这些事件与特定线程或与呼叫线程位于相同桌面中的所有线程相关联。
`id_hook` 要安装的拦截程序类型。
`func` 钩子程序的地址。如果 id_thread 参数为零，或指定不同进程所建立线程的识别码，func参数必须指向 DLL 中的拦截程序。否则，func可以在与目前进程相关联的代码中指向拦截程序。
`h_mod` DLL 的实例句柄，其中包含 func参数所指向的拦截程序。如果id_thread参数指定目前进程所建立的执行绪，而且拦截程序位于与目前进程相关联的代码内，则必须将h_mod参数设定为Null。
*/
pub fn set_windows_hook_ex(
    id_hook: WINDOWS_HOOK_ID,
    func: HOOKPROC,
    h_mod: Option<HINSTANCE>,
    id_thread: u32,
) -> Result<HHOOK> {
    unsafe { SetWindowsHookExW(id_hook, func, h_mod, id_thread) }
}

/**
删除 SetWindowsHookEx 函数安装在钩子链中的挂钩过程。
`h_hook` 要移除的钩子的句柄。此参数是由先前调用 set_windows_hook_ex 获取的钩子句柄。
*/
pub fn unhook_windows_hook_ex(h_hook: HHOOK) -> Result<()> {
    unsafe { UnhookWindowsHookEx(h_hook) }
}

/**
关闭打开的对象句柄。
`h_object` 打开对象的有效句柄。
*/
pub fn close_handle(h_object: HANDLE) {
    unsafe { CloseHandle(h_object) }.unwrap_or(())
}

/**
提取指定模块的模块句柄。调用进程必须已加载模块。
若要避免一节中所述的竞争条件，请使用 get_module_handle_ex 函数。
`module_name` 装入的模块名（.dll或.exe文件）。如果省略扩展名，则会附加预设库副文件名.dll。文件名串可以包含尾端点字符（.），表示模块名称没有扩展名。字符串不需要指定路径。指定路径时，请务必使用反斜线（\），而不是正斜线（/）。名称会独立比较（大小写）目前对应至呼叫进程的地址空间的模块名称。如果此参数为 Null， get_module_handle 将返回用来创建调用进程（.exe文件的文件的句柄）。get_module_handle函数不会撷取使用LOAD_LIBRARY_AS_DATAFILE旗标加载之模组的句柄。
*/
pub fn get_module_handle(module_name: &str) -> HMODULE {
    unsafe { GetModuleHandleW(&HSTRING::from(module_name)) }.expect("Can't get the module handle.")
}

/**
获取当前进程已加载模块的文件的完整路径，该模块必须由当前进程加载。
如果想要获取另一个已加载模块的文件路径，可以使用get_module_file_name_ex函数。
`h_module` 一个模块的句柄。可以是一个DLL模块，或者是一个应用程序的实例句柄。如果该参数为NULL，该函数返回该应用程序全路径。
*/
pub fn get_module_file_name(h_module: Option<HMODULE>) -> String {
    unsafe {
        let mut buf: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
        let len = GetModuleFileNameW(h_module, &mut buf);
        String::from_utf16_lossy(&buf[..len as usize])
    }
}

/**
将指定的模块加载到调用进程的地址空间中。指定的模块可能会导致加载其他模块。有关其他加载选项，请使用 load_library_ex 函数。
`lib_file_name` 模块的名称。这可以是库模块 (.dll 文件)，也可以是可执行模块 (.exe 文件)。如果指定的模块是可执行模块，则不会加载静态导入;相反，模块就像使用标志的 load_library_ex DONT_RESOLVE_DLL_REFERENCES 加载一样。指定的名称是模块的文件名，与库模块本身中存储的名称无关，该名称由 module-definition (.def) 文件中的 LIBRARY 关键字 (keyword) 指定。如果字符串指定完整路径，则函数仅搜索模块的该路径。如果字符串指定相对路径或模块名称而不指定路径，则函数使用标准搜索策略来查找模块;如果函数找不到模块，该函数将失败。指定路径时，请务必使用反斜杠 (\) ，而不是使用 /) (正斜杠。如果字符串指定模块名称而不指定路径，并且省略文件扩展名，则该函数会将默认库扩展“.DLL”追加到模块名称。若要防止函数将“.DLL”追加到模块名称中，请在模块名称字符串中包含尾随点字符 (.)。
*/
pub fn load_library(lib_file_name: &str) -> Result<HMODULE> {
    unsafe { LoadLibraryW(&HSTRING::from(lib_file_name)) }
}

/**
释放加载的动态链接库 (DLL) 模块，并在必要时递减其引用计数。
当引用计数达到零时，模块将从调用进程的地址空间中卸载，句柄不再有效。
`h_lib_module` 已加载的库模块的句柄。load_library、load_library_ex、get_module_handle 或 get_module_handle_ex 函数返回此句柄。
*/
pub fn free_library(h_lib_module: HMODULE) {
    unsafe {
        FreeLibrary(h_lib_module).unwrap_or(());
    }
}

/**
从指定的动态链接库 (DLL) 查询导出函数(也称为过程)或变量的地址。
`h_module` 包含函数或变量的DLL模块的句柄。load_library、load_library_ex、load_packaged_library 或 get_module_handle函数返回此句柄。get_proc_address函数不会从使用LOAD_LIBRARY_AS_DATAFILE标志加载的模块中查询地址。有关详细信息，请参阅load_library_ex。
`proc_name` 函数或变量名称，或函数的序号值。如果此参数是序号值，则它必须在低序位字中；高序位字必须为零。
*/
pub fn get_proc_address(h_module: HMODULE, proc_name: &str) -> FARPROC {
    let name = CString::new(proc_name).unwrap();
    unsafe { GetProcAddress(h_module, PCSTR::from_raw(name.as_ptr().cast())) }
}

/**
查询调用线程的最后错误代码值。 最后一个错误代码按线程进行维护。多个线程不会覆盖彼此的最后错误代码。
*/
pub fn get_last_error() -> WIN32_ERROR {
    unsafe { GetLastError() }
}

/**
查询用户默认 区域设置名称。
注意 如果设计为仅在 Windows Vista 及更高版本上运行，应用程序应优先调用此函数，而不是 get_user_default_lc_id 。
*/
pub fn get_user_default_locale_name() -> String {
    unsafe {
        let mut name: [u16; MAX_LOCALE_NAME as usize] = [0; MAX_LOCALE_NAME as usize];
        let length = GetUserDefaultLocaleName(&mut name);
        String::from_utf16_lossy(&mut name[..(length - 1) as usize])
    }
}

//noinspection SpellCheckingInspection
/**
显示一个模态对话框，其中包含一个系统图标、 一组按钮和一个简短的特定于应用程序消息，如状态或错误的信息。消息框中返回一个整数值，该值指示用户单击了哪个按钮。
`h_wnd` 此参数代表消息框拥有的窗口。如果为NULL，则消息框没有拥有窗口。
`text` 消息框的内容。
`caption` 消息框的标题。
`type` 指定一个决定对话框的内容和行为的位标志集。此参数可以为下列标志组中标志的组合。指定下列标志中的一个来显示消息框中的按钮以及图标。
按钮：
- MB_OK 默认值。有一个确认按钮在里面。
- MB_YESNO 有是和否在里面。
- MB_ABORTRETRYIGNORE 有Abort（放弃），Retry（重试）和Ignore（跳过）
- MB_YESNOCANCEL 消息框含有三个按钮：Yes，No和Cancel
- MB_RETRYCANCEL 有Retry（重试）和Cancel（取消）
- MB_OKCANCEL 消息框含有两个按钮：OK和Cancel
图标：
- MB_ICONEXCLAMATION 一个惊叹号出现在消息框
- MB_ICONWARNING 一个惊叹号出现在消息框
- MB_ICONINFORMATION 一个圆圈中小写字母i组成的图标出现在消息框
- MB_ICONASTERISK 一个圆圈中小写字母i组成的图标出现在消息框
- MB_ICONQUESTION 一个问题标记图标出现在消息框
- MB_ICONSTOP 一个停止消息图标出现在消息框
- MB_ICONERROR 一个停止消息图标出现在消息框
- MB_ICONHAND 一个停止消息图标出现在消息框
形态：
- MB_APPLMODAL 在h_wnd参数标识的窗口中继续工作以前，用户一定响应消息框。但是，用户可以移动到其他线程的窗口且在这些窗口中工作。根据应用程序中窗口的层次机构，用户则以移动到线程内的其他窗口。所有母消息框的子窗口自动地失效，但是弹出窗口不是这样。如果既没有指定MB_SYSTEMMODAL也没有指定MB_TASKMOOAL，则MB_APPLMODAL为缺省的。
- MB_SYSTEMMODAL 除了消息框有WB_EX_TOPMOST类型，MB_APPLMODAL和MB_SYSTEMMODAL一样。用系统模态消息框来改变各种各样的用户，主要的损坏错误需要立即注意（例如，内存溢出）。如果不是那些与hwnd联系的窗口，此标志对用户对窗口的相互联系没有影响。
- MB_TASKMODAL 如果参数hwnd为NULL的话，那么除了所有属于当前线程高层次的窗口失效外，MB_TASKMODALL和MB_APPLMODAL一样。当调用应用程序或库没有一个可以得到的窗口句柄时，使用此标志。但仍需要阻止输入到调用线程的其他窗口，而不是搁置其他线程。
其他：
- MB_DEFAULT_DESKTOP_ONLY 接收输入的当前桌面一定是一个缺省桌面。否则，函数调用失败。缺省桌面是一个在用户已经纪录且以后应用程序在此上面运行的桌面。
- MB_HELP 把一个Help按钮增加到消息框。选择Help按钮或按F1产生一个Help事件。
- MB_RIGHT 文本为右调整
- MB_RTLREADING 用在Hebrew和Arabic系统中从右到左的顺序显示消息和大写文本。
- MB_SETFOREGROUND 消息框变为前景窗口。在内部系统为消息个调用SetForegroundWindow函数。
- MB_TOPMOST 消息框用WS_EX_TOPMOST窗口类型来创建MB_SERVICE_NOTIFICATION。
*/
pub fn message_box(h_wnd: Option<HWND>, text: &str, caption: &str, r#type: MESSAGEBOX_STYLE) {
    unsafe {
        MessageBoxW(h_wnd, &HSTRING::from(text), &HSTRING::from(caption), r#type);
    }
}

//noinspection SpellCheckingInspection
/**
更改指定窗口的属性。函数还将指定偏移量的 32 位 (长整型) 值设置为额外的窗口内存。
注意 此函数已被 set_window_long_ptr 函数取代。若要编写与 32 位和 64 位版本的 Windows 兼容的代码，请使用 set_window_long_ptr 函数。
如果函数成功，则返回值是指定 32 位整数的上一个值。
如果函数失败，则返回值为零。要获得更多的错误信息，请调用 get_last_error。
如果指定的 32 位整数的上一个值为零，并且函数成功，则返回值为零，但函数不会清除最后一个错误信息。这使得很难确定成功或失败。若要处理此问题，在调用 set_window_long 之前，应通过调用 set_last_error 0 来清除最后一个错误信息。然后，函数失败将由返回值零和 get_last_error 结果指示为非零。
某些窗口数据会缓存，因此在调用 set_window_pos 函数之前，使用 set_window_long 所做的更改不会生效。具体而言，如果更改任何框架样式，则必须使用 SWP_FRAMECHANGED 标志调用 set_window_pos，以便正确更新缓存。
如果将 set_window_long 与 GWL_WNDPROC 索引一起使用来替换窗口过程，则窗口过程必须符合 WindowProc 回调函数的说明中指定的准则。
如果将 set_window_long 与 DWL_MSGRESULT 索引一起使用来设置对话过程处理的消息的返回值，则应在之后直接返回 TRUE 。否则，如果调用导致对话过程接收窗口消息的任何函数，嵌套窗口消息可能会覆盖使用 DWL_MSGRESULT 设置的返回值。
使用GWL_WNDPROC索引调用 set_window_long 会创建用于创建窗口的窗口类的子类。应用程序可以子类化系统类，但不应子类化由另一个进程创建的窗口类。set_window_long 函数通过更改与特定窗口类关联的窗口过程来创建窗口子类，导致系统调用新窗口过程而不是上一个窗口过程。应用程序必须通过调用 call_window_proc 将新窗口过程未处理的任何消息传递到上一个窗口过程。这允许应用程序创建窗口过程链。
通过在 与 register_class_ex 函数一起使用的 WNDCLASSEX 结构的 cbWndExtra 成员中指定非零值来保留额外的窗口内存。
不得使用 GWL_HWNDPARENT 索引调用 set_window_long 来更改子窗口的父窗口。请改用 set_parent 函数。
如果窗口的类样式 为CS_CLASSDC 或 CS_OWNDC，请不要将扩展窗口样式设置为 WS_EX_COMPOSITED 或 WS_EX_LAYERED。
调用 set_window_long 以在进度栏上设置样式将重置其位置。
`h_wnd` 窗口的句柄，以及窗口所属类的间接句柄。
`n_index` 要设置的值的从零开始的偏移量。有效值在零到额外窗口内存的字节数之间，减去整数的大小。若要设置任何其他值，请指定以下值之一。
值 | 含义
GWL_EXSTYLE -20 | 设置新的 扩展窗口样式。
GWL_HINSTANCE -6 | 设置新的应用程序实例句柄。
GWL_ID -12 | 设置子窗口的新标识符。 该窗口不能是顶级窗口。
GWL_STYLE -16 | 设置新 窗口样式。
GWL_USERDATA -21 | 设置与窗口关联的用户数据。 此数据供创建窗口的应用程序使用。 其值最初为零。
GWL_WNDPROC -4 | 为窗口过程设置新地址。
如果窗口不属于调用线程所在的进程，则无法更改此属性。当 hWnd 参数标识对话框时，以下值也可用。
值 | 含义
DWL_DLGPROC DWLP_MSGRESULT + sizeof (LRESULT) | 设置对话框过程的新地址。
DWL_MSGRESULT 0 | 设置对话框过程中处理的消息的返回值。
DWL_USER DWLP_DLGPROC + sizeof (DLGPROC) | 设置应用程序专用的新额外信息，例如句柄或指针。
`new_long` 替换值。
*/
pub fn set_window_long(h_wnd: HWND, n_index: WINDOW_LONG_PTR_INDEX, new_long: i32) -> i32 {
    unsafe { SetWindowLongW(h_wnd, n_index, new_long) }
}

//noinspection SpellCheckingInspection
/**
更改指定窗口的属性。 函数还会在额外窗口内存中的指定偏移量处设置值。
注意 若要编写与 32 位和 64 位版本的 Windows 兼容的代码，请使用 set_window_long_ptr。为 32 位 Windows 编译时， set_window_long_ptr 定义为对 set_window_long 函数的调用。
如果函数成功，则返回值是指定偏移量的上一个值。
如果函数失败，则返回值为零。要获得更多的错误信息，请调用 get_last_error。
如果上一个值为零且函数成功，则返回值为零，但函数不清除最后一个错误信息。若要确定成功或失败，请通过调用 0 的 set_last_error 清除最后一个错误信息，然后调用 set_window_long_ptr。函数失败将由返回值零和 get_last_error 结果指示为非零。
某些窗口数据会缓存，因此在调用 set_window_pos 函数之前，使用 set_window_long_ptr 所做的更改不会生效。
如果将 set_window_long_ptr 与 GWLP_WNDPROC 索引一起使用来替换窗口过程，则窗口过程必须符合 WindowProc 回调函数的说明中指定的准则。
如果使用 set_window_long_ptr和DWLP_MSGRESULT 索引来设置对话框过程处理的消息的返回值，则对话框过程应随后直接返回 TRUE 。否则，如果调用导致对话框过程接收窗口消息的任何函数，嵌套窗口消息可能会覆盖使用 DWLP_MSGRESULT 设置的返回值。
使用 GWLP_WNDPROC 索引调用 set_window_long_ptr 会创建用于创建窗口的窗口类的子类。应用程序可以子类化系统类，但不应子类化由另一个进程创建的窗口类。set_window_long_ptr 函数通过更改与特定窗口类关联的窗口过程来创建窗口子类，导致系统调用新窗口过程而不是上一个窗口过程。应用程序必须通过调用 call_window_proc 将新窗口过程未处理的任何消息传递到上一个窗口过程。这允许应用程序创建窗口过程链。
通过在 与 register_class_ex 函数一起使用的 WNDCLASSEX 结构的 cbWndExtra 成员中指定非零值来保留额外的窗口内存。
请勿使用 GWLP_HWNDPARENT 索引调用 set_window_long_ptr 来更改子窗口的父窗口。请改用 set_parent 函数。
如果窗口的类样式 为CS_CLASSDC 或 CS_PARENTDC，请不要将扩展窗口样式设置为 WS_EX_COMPOSITED 或 WS_EX_LAYERED。
调用 set_window_long_ptr 在进度栏上设置样式将重置其位置
`h_wnd` 窗口的句柄，以及窗口所属类的间接句柄。如果拥有由 h_wnd 参数指定的窗口的进程在 UIPI 层次结构中的进程特权高于调用线程所在的进程，则 set_window_long_ptr 函数将失败。
Windows XP/2000： 如果 h_wnd 参数指定的窗口不属于调用线程所在的进程，set_window_long_ptr 函数将失败。
`n_index` 要设置的值的从零开始的偏移量。有效值介于零到额外窗口内存的字节数之间，减去 LONG_PTR的大小。若要设置任何其他值，请指定以下值之一。
值 | 含义
GWL_EXSTYLE -20 | 设置新的 扩展窗口样式。
GWLP_HINSTANCE -6 | 设置新的应用程序实例句柄。
GWLP_ID -12 | 设置子窗口的新标识符。 该窗口不能是顶级窗口。
GWL_STYLE -16 | 设置新 窗口样式。
GWLP_USERDATA -21 | 设置与窗口关联的用户数据。 此数据供创建窗口的应用程序使用。 其值最初为零。
GWLP_WNDPROC -4 | 为窗口过程设置新地址。
当 hWnd 参数标识对话框时，以下值也可用。
值 | 含义
DWLP_DLGPROC DWLP_MSGRESULT + sizeof (LRESULT) | 设置指向对话框过程的新指针。
DWLP_MSGRESULT 0 | 设置对话框过程中处理的消息的返回值。
DWLP_USER DWLP_DLGPROC + sizeof (DLGPROC) | 设置应用程序专用的新额外信息，例如句柄或指针
`new_long` 替换值。
*/
pub fn set_window_long_ptr(h_wnd: HWND, n_index: WINDOW_LONG_PTR_INDEX, new_long: isize) -> isize {
    #[cfg(target_arch = "x86")]
    let new_long = new_long as i32;
    let res = unsafe { SetWindowLongPtrW(h_wnd, n_index, new_long) };
    #[cfg(target_arch = "x86")]
    let res = res as isize;
    res
}

//noinspection SpellCheckingInspection
/**
将消息信息传递到指定的窗口过程。
返回值指定消息处理的结果，并取决于发送的消息。
使用 call_window_proc 函数进行窗口子类化。通常，具有相同类的所有窗口共享一个窗口过程。子类是具有相同类的窗口或窗口集，其消息被另一个窗口过程截获和处理， (或) 过程，然后再传递到类的窗口过程。
set_window_long 函数通过更改与特定窗口关联的窗口过程来创建子类，导致系统调用新窗口过程而不是上一个窗口过程。应用程序必须通过调用 call_window_proc 将新窗口过程未处理的任何消息传递到上一个窗口过程。这允许应用程序创建窗口过程链。
如果定义了 STRICT ，则 prev_wnd_func 参数的数据类型为 WNDPROC。WNDPROC 类型声明如下：
```c
LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);
```
如果未定义 STRICT ，则 prev_wnd_func 参数的数据类型为 FARPROC。FARPROC 类型声明如下：
```c
int (FAR WINAPI * FARPROC) ()
```
在 C 中， FARPROC 声明指示具有未指定参数列表的回调函数。 但是，在 C++ 中，声明中的空参数列表指示函数没有参数。这种微妙的区别可能会破坏粗心的代码。下面是处理这种情况的一种方法：
```c
#ifdef STRICT
  WNDPROC MyWindowProcedure
#else
  FARPROC MyWindowProcedure
#endif
// ...
lResult = CallWindowProc(MyWindowProcedure, ...);
```
有关使用空参数列表声明的函数的详细信息，请参阅 Bjarne Stroustrup 的 C++ 编程语言第二版。
call_window_proc 函数处理 Unicode 到 ANSI 的转换。如果直接调用窗口过程，则无法利用此转换。
`prev_wnd_func` 上一个窗口过程。如果通过调用 get_window_long 函数来获取此值，且 n_index 参数设置为 GWL_WNDPROC 或 DWL_DLGPROC，则它实际上是窗口或对话框过程的地址，或者仅对 call_window_proc 有意义的特殊内部值。
`h_wnd` 用于接收消息的窗口过程的句柄。
`msg` 消息。
`w_param` 其他的消息特定信息。 此参数的内容取决于 msg 参数的值。
`l_param` 其他的消息特定信息。 此参数的内容取决于 msg 参数的值。
*/
pub fn call_window_proc(
    prev_wnd_func: WNDPROC,
    h_wnd: HWND,
    msg: u32,
    w_param: WPARAM,
    l_param: LPARAM,
) -> LRESULT {
    unsafe { CallWindowProcW(prev_wnd_func, h_wnd, msg, w_param, l_param) }
}

//noinspection SpellCheckingInspection
/**
查询有关指定窗口的信息。该函数还会在指定偏移量处查询 32 位 （DWORD） 值，并将其查询到额外的窗口内存中。
注意如果要查询指针或句柄，则此函数已被 get_window_long_ptr 函数取代。（指针和手柄在 32 位 Windows 上为 32 位，在 64 位 Windows 上为 64 位。要编写与 32 位和 64 位版本的 Windows 兼容的代码，请使用 get_window_long_ptr。
如果函数成功，则返回值为请求的值。
如果函数失败，则返回值为零。要获取扩展错误信息，请调用 get_last_error。
如果之前未调用 set_window_long，则 get_window_long 为额外窗口或类内存中的值返回零。
通过在 与 register_class_ex 函数一起使用的 WNDCLASSEX 结构的 cbWndExtra 成员中指定非零值来保留额外的窗口内存。
`h_wnd` 窗口的句柄，间接地，窗口所属的类。
`n_index` 要查询的值的从 0 开始的偏移量。有效值的范围是 0 到额外窗口内存的字节数减去 4;例如，如果指定了 12 个字节或更多字节的额外内存，则值 8 将是第三个 32 位整数的索引。要查询任何其他值，请指定以下值之一。
值 | 意义
GWL_EXSTYLE -20 | 查询扩展窗口样式。
GWL_HINSTANCE -6 | 查询应用程序实例的句柄。
GWL_HWNDPARENT -8 | 查询父窗口的句柄（如果有）。
GWL_ID -12 | 查询窗口的标识符。
GWL_STYLE -16 | 查询窗口样式。
GWL_USERDATA -21 | 查询与窗口关联的用户数据。此数据供创建窗口的应用程序使用。它的值最初为零。
GWL_WNDPROC -4 | 查询窗口过程的地址，或表示窗口过程地址的句柄。必须使用 call_window_proc 函数调用窗口过程。
当 h_wnd 参数标识对话框时，以下值也可用。
值 | 意义
DWL_DLGPROC DWLP_MSGRESULT + sizeof(LRESULT) | 查询对话框过程的地址，或表示对话框过程地址的句柄。必须使用 CallWindowProc 函数调用对话框过程。
DWL_MSGRESULT 0 | 查询在对话框过程中处理的消息的返回值。
DWL_USER DWLP_DLGPROC + sizeof (DLGPROC) | 查询应用程序专用的额外信息，例如句柄或指针。
*/
pub fn get_window_long(h_wnd: HWND, n_index: WINDOW_LONG_PTR_INDEX) -> i32 {
    unsafe { GetWindowLongW(h_wnd, n_index) }
}

//noinspection SpellCheckingInspection
/**
查询有关指定窗口的信息。函数还会将指定偏移量的值查询到额外的窗口内存中。
注意 若要编写与 32 位和 64 位版本的 Windows 兼容的代码，请使用 get_window_long_ptr。为 32 位 Windows 编译时， get_window_long_ptr 定义为对 get_window_long 函数的调用。
如果函数成功，则返回值是请求的值。
如果函数失败，则返回值为零。 要获得更多的错误信息，请调用 get_last_error。
如果以前未调用 set_window_long 或 set_window_long_ptr ， 则 get_window_long_ptr 为额外窗口或类内存中的值返回零。
通过在与 register_class_ex 函数一起使用的 WNDCLASSEX 结构的 cbWndExtra 成员中指定非零值来保留额外的窗口内存。
`h_wnd` 窗口的句柄，以及窗口所属类的间接句柄。
`n_index` 要查询的值的从零开始的偏移量。有效值介于零到额外窗口内存的字节数之间，减去 LONG_PTR的大小。若要查询任何其他值，请指定以下值之一。
值 | 含义
GWL_EXSTYLE -20 | 查询扩展窗口样式。
GWLP_HINSTANCE -6 | 查询应用程序实例的句柄。
GWLP_HWNDPARENT -8 | 查询父窗口的句柄（如果有）。
GWLP_ID -12 | 查询窗口的标识符。
GWL_STYLE -16 | 查询窗口样式。
GWLP_USERDATA -21 | 查询与窗口关联的用户数据。此数据供创建窗口的应用程序使用。其值最初为零。
GWLP_WNDPROC -4 | 查询指向窗口过程的指针，或表示指向窗口过程的指针的句柄。必须使用 call_window_proc 函数调用窗口过程。
当 h_wnd 参数标识对话框时，以下值也可用。
值 | 含义
DWLP_DLGPROC DWLP_MSGRESULT + sizeof (LRESULT) | 查询指向对话框过程的指针，或表示指向对话框过程的指针的句柄。必须使用 call_window_proc 函数调用对话框过程。
DWLP_MSGRESULT 0 | 查询对话框过程中处理的消息的返回值。
DWLP_USER DWLP_DLGPROC + sizeof (DLGPROC) | 查询应用程序专用的额外信息，例如句柄或指针。
*/
pub fn get_window_long_ptr(h_wnd: HWND, n_index: WINDOW_LONG_PTR_INDEX) -> isize {
    let res = unsafe { GetWindowLongPtrW(h_wnd, n_index) };
    #[cfg(target_arch = "x86")]
    let res = res as isize;
    res
}
